define([
  'd3v3',
  'lodash'
], function(d3, _) {
  'use strict';
  

/**
 * [绘制气泡图]
 * @param  {[type]} data [series]
 * @param  {[type]} width [description]
 * @param  {[type]} height [description]
 * @param  {[type]} selector [description]
 * @param  {[type]} hideEl [用于计算文本大小的隐藏节点]
 * @param  {[type]} gravityParam [分布系数：0~0.1（0.1是正圆，0是最大限度的椭圆）]
 * @param  {[type]} isTransform [需要纵向布局，需要旋转]
 * @param  {[type]} hasEffect [开启动画]
 * @return {[type]} chart [description]
 *
 * http://vallandingham.me/bubble_cloud/?alice#cook
 */
function bubbles(
  data = [],
  width,
  height,
  selector,
  hideEl,
  gravityParam = 0.01,
  isTransform,
  hasEffect
) {
  let chargeParam = -400 //电荷强度：正数节点相吸，负数节点相斥
  let node = null
  let effectCircles = null
  let label = null

  let margin = {
    top: 0,
    right: 0,
    bottom: 0,
    left: 0,
  }
  let maxRadius = 65 //largest size for our bubbles
  let rScale = d3.scale.sqrt().range([0, maxRadius]) //this scale will be used to size our bubbles
  let rValue = (d) => {
    return parseInt(d.value)
  }
  let idValue = (d) => {
    return d.name
  }
  let textValue = (d) => {
    return d.name
  }
  let collisionPadding = 4
  let minCollisionRadius = 12
  let jitter = 0.1 //感觉是抖动系数，默认是0.5时抖得太厉害，不能设置为0，否则气泡会重叠

  let transformData = function (rawData) {
    rawData.forEach(function (d) {
      d.value = parseInt(d.value)
      return rawData.sort(function () {
        return 0.5 - Math.random()
      })
    })
    return rawData
  }

  let gravity = function (alpha) {
    let ax, ay, cx, cy
    cx = width / 2
    cy = height / 2
    ax = alpha / 8
    ay = alpha
    return function (d) {
      d.x += (cx - d.x) * ax
      return (d.y += (cy - d.y) * ay)
    }
  }

  let collide = function (jitter) {
    return function (d) {
      return data.forEach(function (d2) {
        let distance, minDistance, moveX, moveY, x, y
        if (d !== d2) {
          x = d.x - d2.x
          y = d.y - d2.y
          distance = Math.sqrt(x * x + y * y)
          minDistance = d.forceR + d2.forceR + collisionPadding
          if (distance < minDistance) {
            distance = ((distance - minDistance) / distance) * jitter
            moveX = x * distance
            moveY = y * distance
            d.x -= moveX
            d.y -= moveY
            d2.x += moveX
            return (d2.y += moveY)
          }
        }
      })
    }
  }

  let scaleG = function () {
    //处理g和svg之间的缩放问题
    let gRect = node[0].parentNode && node[0].parentNode.getBBox() //该方法获取到的宽和高，是不受画布缩放影响的g节点宽高（为了兼容IE）
    let gWidth = gRect.width
    let gHeight = gRect.height
    let svgWidth = width //所以svg的宽高也要以没有经过画布缩放的宽高来计算
    let svgHeight = height //所以svg的宽高也要以没有经过画布缩放的宽高来计算
    let gDivisor = gWidth / gHeight //g宽高比
    let svgDivisor = svgWidth / svgHeight //svg宽高比
    let scale

    //若svg宽高比 > g宽高比，则用高度进行缩放；反之用宽度
    if (svgDivisor <= gDivisor) {
      scale = (svgWidth * 0.8) / gWidth
    } else if (svgDivisor > gDivisor) {
      scale = (svgHeight * 0.8) / gHeight
    }

    if (!isNaN(scale)) {
      if (isTransform) {
        /*竖向布局*/
        //计算偏移（矫正中心点位置）
        let translateX = (height - width) / 2
        let translateY = (width - height) / 2

        //注意先后顺序：
        //先偏移
        //再旋转（对该节点以宽作高，以高作宽，然后矫正节点的位置，让他旋转90度后，依然跟widget吻合）
        //最后缩放
        selector.style.transform =
          'translate(' +
          translateX +
          'px, ' +
          translateY +
          'px) rotate(90deg) scale(' +
          scale +
          ')'
      } else {
        /*横向布局，只需要缩放*/
        selector.style.transform = 'scale(' + scale + ')'
      }
    }
  }

  let tick = function (e) {
    let dampenedAlpha
    dampenedAlpha = e.alpha * 0.1
    node
      .each(gravity(dampenedAlpha))
      .each(collide(jitter))
      .attr('transform', function (d) {
        scaleG() //处理g和svg之间的缩放问题

        return 'translate(' + d.x + ',' + d.y + ')'
      })

    label
      .style('left', function (d) {
        return margin.left + d.x - d.dx / 2 + 'px'
      })
      .style('top', function (d) {
        return margin.top + d.y - d.dy / 2 + 'px'
      })
  }

  let end = function(e) {
    if (hasEffect && effectCircles) {
      effectCircles.append('animateTransform')
        .attr('attributeName', 'transform')
        .attr('attributeType', 'XML')
        .attr('type', 'rotate')
        .attr('from', '0')
        .attr('to', '360')
        .attr('dur', '3s')
        .attr('repeatCount', 'indefinite')
    }
  }

  //这几个参数是控制布局最关键的参数
  let force = d3.layout
    .force()
    .gravity(gravityParam)
    .charge(chargeParam)
    .size([width, height])
    .on('tick', tick)
    .on('end', end)

  /**
   * [计算文本需要按基数缩放的倍数]
   * @param  {[type]} d [description]
   * @return {[type]}   [description]
   */
  let parseFontScale = function (text, circleR) {
    //获取默认占的宽度
    hideEl.innerHTML = text
    let rect = hideEl.getBoundingClientRect()
    let _width = rect.width
    let _height = rect.height

    let fontScale = +(circleR * 2 * 0.7) / _width
    if (_height > _width) {
      fontScale = +(circleR * 2 * 0.7) / _height
    }
    return fontScale
  }

  let updateNodes = function (svg) {
    node = node.selectAll('.bubble-node').data(data, function (d) {
      return idValue(d)
    })
    node.exit().remove()

    if (!hasEffect) {
      node
        .enter()
        .append('a')
        .attr('class', 'bubble-node')
        .append('circle')
        .attr('r', function (d) {
          return rScale(rValue(d))
        })
        .attr('fill', function (d) {
          //填充气泡颜色
          let fill = d.color
          return fill
        })
    } else {
      const defs = svg.select("defs").empty()?svg.append("defs"):svg.select("defs");

      const bubbleNode = node.enter().append('a').attr('class', 'bubble-node');

      effectCircles = bubbleNode.append('circle')
        .attr('r', function(d) {
          return rScale(rValue(d));
        })
        .attr('fill', function(d) {
          const id = `bubble-nodelight-gradien-${d.index}`;
          let gradient = defs.select(`#${id}`);
          if (gradient.empty()) {
            gradient = defs.append('linearGradient').attr('id', id)
            gradient.append('stop').attr('class', 'stop-1').attr('offset', '0%');
            gradient.append('stop').attr('class', 'stop-2').attr('offset', '40%');
            gradient.append('stop').attr('offset', '100%').attr('stop-color', `hsla(0,0%,0%,0)`);
          }
          gradient.select('.stop-1').attr('stop-color', `hsl(${d.colorHslArr[0]},${d.colorHslArr[1]+2}%,${d.colorHslArr[2]+4}%)`)
          gradient.select('.stop-2').attr('stop-color', `hsla(${d.colorHslArr[0]},${d.colorHslArr[1]+2}%,${d.colorHslArr[2]+4}%,0.2)`)

          return `url(#${id})`;
        })
        .attr('transform', 'rotate(0)')

      bubbleNode.append('circle').attr('r', function(d) {
        return rScale(rValue(d)) * 7 / 12;
      }).attr('fill', function(d) {
        //填充气泡颜色
        let fill = d.color
        return fill
      }).attr('stroke', function(d) {
        return `hsl(${d.colorHslArr[0]},${d.colorHslArr[1]/2}%,${d.colorHslArr[2]/2}%)`
      }).attr('stroke-width', function(d) {
        return rScale(rValue(d)) / 6;
      })
    }
  }

  let updateLabels = function () {
    let labelEnter
    label = label.selectAll('.bubble-label').data(data, function (d) {
      return idValue(d)
    })
    label.exit().remove()
    labelEnter = label.enter().append('a').attr('class', 'bubble-label')
    //label:name
    labelEnter
      .append('div')
      .attr('class', 'bubble-label-name')
      .text(function (d) {
        return textValue(d)
      })

    //label:count
    labelEnter
      .append('div')
      .attr('class', 'bubble-label-value')
      .text(function (d) {
        return parseInt(d.originValue) //用于计算气泡半径的value是经过处理的，但是显示count还是要显示真实的原数据（originValue）
        // return rValue(d);
      })
      .style('font-weight', 'bold')
      .style('transform', function (d) {
        let fontNameScale = parseFontScale(d.name, d.forceR) //整个bubble-label的scale

        //描述：需要单独为value赋上的scale = 用value的文本计算出来的scale / bubble-label的scale
        let fontCountScale = parseFontScale(d.value, d.forceR) / fontNameScale

        //为了保持原来的布局不被破坏，只有超出了的气泡的value才进行缩放，所以大于1的也都按1算
        if (fontCountScale > 1) {
          fontCountScale = 1
        }
        return 'scale(' + fontCountScale + ')'
      })

    label.style('font-size', '12px')
    label.style('transform', function (d) {
      let fontScale = parseFontScale(d.name, d.forceR)

      if (hasEffect) {
        fontScale /= 2
      }

      let transformStyle = 'scale(' + fontScale + ')' //基数都是12的基础上，通过scale控制文本大小

      if (isTransform) {
        //纵向布局，通过样式旋转
        transformStyle = 'scale(' + fontScale + ') rotate(-90deg)'
      }
      return transformStyle
    })

    label
      .append('span')
      .text(function (d) {
        return textValue(d)
      })
      .each(function (d) {
        let fontScale = parseFontScale(d.name, d.forceR)
        //bubble-label上加了transform，所以计算的时候要除回这个scale
        d.dx = Math.max(2 * d.forceR, this.getBoundingClientRect().width) / fontScale
      })
      .remove()

    label.style('width', function (d) {
      return d.dx + 'px'
    })

    label.each(function (d) {
      let fontScale = parseFontScale(d.name, d.forceR)

      if (hasEffect && !isTransform) { //硬凑的参数，也不知道原理是啥
        fontScale /= 2
      }
      //bubble-label上加了transform，所以计算的时候要除回这个scale
      d.dy = this.getBoundingClientRect().height / fontScale
    })
  }

  let update = function (svg) {
    data.forEach((d) => {
      d.forceR = Math.max(minCollisionRadius, rScale(rValue(d)))
    })
    force.nodes(data).start()

    updateNodes(svg)
    updateLabels()
  }

  let chart = function (selection) {
    return selection.each(function (rawData) {
      let maxDomainValue, svg, svgEnter
      data = transformData(rawData)
      maxDomainValue = d3.max(data, function (d) {
        return rValue(d)
      })
      rScale.domain([0, maxDomainValue])

      svg = d3.select(selector).selectAll('svg').data([data])
      svgEnter = svg.enter().append('svg')
      svg.attr('width', width + margin.left + margin.right)
      svg.attr('height', height + margin.top + margin.bottom)
      svg.style('overflow', 'visible') //超出的也要显示

      node = svgEnter.append('g').attr('class', 'bubble-nodes')

      label = d3
        .select(selector)
        .selectAll('.bubble-labels')
        .data([data])
        .enter()
        .append('div')
        .attr('class', 'bubble-labels')

      update(svg)
    })
  }

  chart.jitter = function (_) {
    if (!arguments.length) {
      return jitter
    }
    jitter = _
    force.start()
    return chart
  }
  chart.height = function (_) {
    if (!arguments.length) {
      return height
    }
    height = _
    return chart
  }
  chart.width = function (_) {
    if (!arguments.length) {
      return width
    }
    width = _
    return chart
  }
  chart.r = function (_) {
    if (!arguments.length) {
      return rValue
    }
    rValue = _
    return chart
  }

  return chart
}

return bubbles

});